﻿#region License
// Copyright (c) 2009 Shengmin Zhang
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Reflection;
using System.Web;
using System.Collections.Generic;
using System.Text;
using System.Web.Caching;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Collections.Specialized;
using System.Web.SessionState;

//using Apprender.Utilities;
using Apprender.Serialization;
using Apprender.Reflection;
using Apprender.Web.Configuration;
using Apprender.Configuration;
using Apprender.Serialization.Json;
using Apprender.Web.Caching;
using Apprender.Compression;
using Apprender.Extensions.Methods;

namespace Apprender.Web
{
    public class DataService : IHttpHandler
    {
        #region Constructors
        public DataService()
        {
            JSerializer serializer = new JSerializer();
            var mapping = new ParameterMappingResolver();

            this._cacheProvider = new CacheProvider();
            this._compressionProvider = new CompressionProvider();
            this._jsonSerializer = serializer;
            this._jsonParameterSerializer = serializer;
            this._paramMappingResolver = mapping;
            this._jsonParameterMappingResolver = mapping;
            
        }

        public DataService(IParameterMappingResolver paramMappingResolver,
            IJsonParameterMappingResolver jsonParamMappingResolver,
            IJsonParameterSerializer jsonParamSerializer,
            IJsonSerializer jsonSerializer,
            ICacheProvider cacheProvider,
            ICompressionProvider compressionProvider)
        {
            this._jsonParameterMappingResolver = jsonParamMappingResolver;
            this._jsonParameterSerializer = jsonParamSerializer;
            this._jsonSerializer = jsonSerializer;
            this._paramMappingResolver = paramMappingResolver;
            this._compressionProvider = compressionProvider;
            this._cacheProvider = cacheProvider;
        }

        #endregion

        #region Properties & Fields

        public virtual bool IsReusable
        {
            get { return false; }
        }

        private HttpRequest _request;
        protected HttpRequest Request { get { return this._request; } }

        private HttpSessionState _session;
        protected HttpSessionState Session { get { return this._session; } }

        private HttpContext _context;
        protected HttpContext Context { get { return this._context; } }

        private HttpResponse _response;
        protected HttpResponse Response { get { return this._response; } }

        private Cache _cache;
        protected Cache Cache { get { return this._cache; } }

        private HttpRequestContentType _contentType;
        protected HttpRequestContentType ContentType { get { return _contentType; } }

        private CompressionEncoding _compressionEncoding;
        protected CompressionEncoding CompressionEncoding { get { return this._compressionEncoding; } }

        private IJsonSerializer _jsonSerializer;
        protected virtual IJsonSerializer JsonSerializer
        {
            get { return this._jsonSerializer; }
        }
        private IJsonParameterSerializer _jsonParameterSerializer;
        protected virtual IJsonParameterSerializer JsonParameterSerializer
        {
            get { return this._jsonParameterSerializer; }
        }

        private IParameterMappingResolver _paramMappingResolver;
        protected virtual IParameterMappingResolver ParameterMappingResolver
        {
            get { return this._paramMappingResolver; }
        }
        private IJsonParameterMappingResolver _jsonParameterMappingResolver;
        protected virtual IJsonParameterMappingResolver JsonParameterMappingResolver
        {
            get { return this._jsonParameterMappingResolver; }
        }

        private ICacheProvider _cacheProvider;
        protected virtual ICacheProvider CacheProvider
        {
            get { return this._cacheProvider; }
        }

        private ICompressionProvider _compressionProvider;
        protected virtual ICompressionProvider CompressionProvider
        {
            get { return this._compressionProvider; }
        }

        private IHttpHandler _handler;
        private string _fullMethodName;


        private string _methodName;
        protected string MethodName
        {
            get { return this._methodName; }
        }

        private string _fullHandlerName;
        protected string FullTypeName
        {
            get { return this._fullHandlerName; }
        }

        private Type _handlerType;


        #endregion

        #region Private Methods

        private void CacheInternal(string key, object value)
        {
            _cache.Insert(key, value, null, Cache.NoAbsoluteExpiration, TimeSpan.FromDays(1), CacheItemPriority.High, null);
        }

        private MethodInfo GetMethodInfo()
        {
            var cacheKey = _fullMethodName + "method";

            var method = _cache.Get(cacheKey) as MethodInfo;
            if (method == null)
            {
                method = _handlerType.GetMethod(_methodName);
                CacheInternal(cacheKey, method);
            }

            return method;
        }

        private ParameterInfo[] GetParameterInfo(MethodInfo method)
        {
            var cacheKey = _fullMethodName + "param";
            var info = _cache.Get(cacheKey) as ParameterInfo[];

            if (info == null)
            {
                info = method.GetParameters();
                CacheInternal(cacheKey, info);
            }
            return info;
        }        

        private DataServiceMethodAttribute GetDataServiceMethodAttrbute(MethodInfo method)
        {
            string cacheKey = _fullMethodName + "attr";

            DataServiceMethodAttribute attr =_cache.Get(cacheKey) as DataServiceMethodAttribute;
            if (attr == null)
            {
                object[] attrs = method.GetCustomAttributes(typeof(DataServiceMethodAttribute), false);
                if (attrs.Length > 0)
                {
                    attr = attrs[0] as DataServiceMethodAttribute;
                }
                else
                {
                    attr = new DataServiceMethodAttribute();
                }
                CacheInternal(cacheKey, attr);
            }

            return attr;
        }        

        private byte[] Compress(byte[] data)
        {
            //if (CompressionProvider == null)
                //_compressionProvider = new CompressionProvider();
            return CompressionProvider.GZipCompress(data);
        }

        private void InsertCache(string key, byte[] value, ICacheSetting setting)
        {
            CacheProvider.InsertCache(key, value, _context, setting);
        }

        private byte[] GetCache(string key, ICacheSetting setting)
        {
            //if (CacheProvider == null)
                //_cacheProvider = new CacheProvider();

            if (string.Equals(setting.CacheDirectory, string.Empty))
                setting.CacheDirectory = CacheProvider.DefaultCacheDirectory;

            return CacheProvider.GetCache(key, _context, setting);
        }

        private object[] MapParameters(ParameterInfo[] paramInfo)
        {
            switch (_contentType)
            {
                case HttpRequestContentType.QueryString:
                    return MapParameters(_request.QueryString, paramInfo);
                case HttpRequestContentType.Form:
                    return MapParameters(_request.Form, paramInfo);
                case HttpRequestContentType.Json:
                    return MapParameters(_request.GetInput(), paramInfo);
            }
            return null;
        }

        private object[] MapParameters(string json, ParameterInfo[] paramInfo)
        {
            //if (JsonParameterMappingResolver == null)
                //_paramMappingResolver = new ParameterMappingResolver();

            //if (JsonParameterSerializer == null)
                //_jsonSerializer = new JSerializer();

            return JsonParameterMappingResolver.Map(JsonParameterSerializer.Deserialize(json, paramInfo.Length), paramInfo, JsonSerializer);
        }

        private object[] MapParameters(Dictionary<string, string> paramList, ParameterInfo[] paramInfo)
        {
            //if (JsonParameterMappingResolver == null)
                //_paramMappingResolver = new ParameterMappingResolver();

            return JsonParameterMappingResolver.Map(paramList, paramInfo, JsonSerializer);
        
        }

        private object[] MapParameters(NameValueCollection paramList, ParameterInfo[] paramInfo)
        {
           // if (ParameterMappingResolver == null)
                //_paramMappingResolver = new ParameterMappingResolver();

            return ParameterMappingResolver.Map(paramList, paramInfo);
        }

        private object[] MapParameters(ParameterInfo[] paramInfo, out string cacheKey, ICacheSetting setting)
        {
            //if(CacheProvider == null)
               // _cacheProvider = new CacheProvider();

            switch (_contentType)
            {
                case HttpRequestContentType.QueryString:
                    cacheKey = CacheProvider.GetCacheKey(_request.QueryString, _fullHandlerName, _methodName, _compressionEncoding, setting.CacheKeyElements);
                    return MapParameters(_request.QueryString, paramInfo); //Deserialize(_request.QueryString, paramInfo);
                case HttpRequestContentType.Form:
                    var form = _request.GetInput();
                    cacheKey = CacheProvider.GetCacheKey(_request.Form, _fullHandlerName, _methodName, _compressionEncoding, setting.CacheKeyElements);
                    return MapParameters(_request.Form, paramInfo);
                case HttpRequestContentType.Json:
                    var json = _request.GetInput();
                    var paramList = Deserialize(json, paramInfo.Length);
                    cacheKey = CacheProvider.GetCacheKey(paramList, _fullHandlerName, _methodName, _compressionEncoding, setting.CacheKeyElements);//_fullMethodName + "result" + HttpUtility.UrlEncode(json) + _compressionEncoding.ToString();
                    return MapParameters(paramList, paramInfo);
            }

            cacheKey = _fullMethodName + "result" + _compressionEncoding.ToString();
            return null;
        }

        

        private Dictionary<string, string> Deserialize(string json, int count)
        {
             //if (JsonParameterSerializer == null)
                      //  _jsonSerializer = new JSerializer();

             return JsonParameterSerializer.Deserialize(json, count);
        }

        private byte[] Serialize(object result, HttpResponseContentType contentType)
        {
            string json = string.Empty;

            switch (contentType)
            {
                case HttpResponseContentType.Custom:
                    json = result.ToString();
                    break;
                case HttpResponseContentType.Json:
                    _response.ContentType = "application/json";
                   // if (JsonSerializer == null)
                        //_jsonSerializer = new JSerializer();
                    json = JsonSerializer.Serialize(result);
                    break;
                case HttpResponseContentType.JavaScript:
                    _response.ContentType = "application/x-javascript";
                    json = result as string;
                    break;
                case HttpResponseContentType.Css:
                    _response.ContentType = "text/css";
                    json = result as string;
                    break;
                case HttpResponseContentType.Text:
                    _response.ContentType = "text/plain";
                    json = result as string;
                    break;
                case HttpResponseContentType.Html:
                    _response.ContentType = "text/html";
                    json = result as string;
                    break;
                case HttpResponseContentType.Xml:
                    _response.ContentType = "application/xml";
                    json = result as string;
                    break;
            }
            

            return Encoding.UTF8.GetBytes(json);
        }

        private void SetResponse(HttpResponseContentType contentType)
        {
            switch (contentType)
            {
               
                case HttpResponseContentType.Json:
                    _response.ContentType = "application/json";                    
                    break;
                case HttpResponseContentType.JavaScript:
                    _response.ContentType = "application/x-javascript";
                   
                    break;
                case HttpResponseContentType.Css:
                    _response.ContentType = "text/css";
                    
                    break;
                case HttpResponseContentType.Text:
                    _response.ContentType = "text/plain";
                    
                    break;
                case HttpResponseContentType.Html:
                    _response.ContentType = "text/html";
                    
                    break;
                case HttpResponseContentType.Xml:
                    _response.ContentType = "application/xml";
                    
                    break;
            }
        }

        #endregion

        #region Public Methods

        public void ProcessRequest(HttpContext context)
        {
            _context = context;
            _request = _context.Request;
            _response = _context.Response;
            _session = _context.Session;

            _contentType = _request.GetContentType();
            _compressionEncoding = _request.GetAcceptEncoding();
            _cache = HttpRuntime.Cache;
            _response.Charset = "utf-8";

            //setting up method info
            _handler = _context.CurrentHandler;
            _methodName = Path.GetFileNameWithoutExtension(_request.PathInfo);
            _handlerType = _handler.GetType();
            _fullHandlerName = _handlerType.FullName;
            _fullMethodName = _fullHandlerName + _methodName;
           

            var method = GetMethodInfo();
            var isVoid = method.IsVoid();
            var paramInfo = GetParameterInfo(method);

            if (isVoid)
            {
                object[] paramList = MapParameters(paramInfo);

                try
                {
                    method.Invoke(_handler, paramList);                    
                    _response.End();
                    return;
                }
                catch (Exception ex)
                {
                    throw ex;
                }              

            }
            else
            {
                DataServiceMethodAttribute attr = GetDataServiceMethodAttrbute(method);
                bool enableCaching = (attr.CacheDuration > 0) ? true : false;
                byte[] result = null;

                object[] paramList = null;
                if (enableCaching)
                {
                    string resultCacheKey = string.Empty;
                    paramList = MapParameters(paramInfo, out resultCacheKey, attr);

                    result = GetCache(resultCacheKey, attr);//CacheProvider.GetCache(resultCacheKey, _context, attr);

                    if (result != null)
                    {
                        SetResponse(attr.ResponseContentType);
                        if (attr.EnableCompression &&_compressionEncoding == CompressionEncoding.GZip)
                        {
                            _response.AppendHeader("Content-Encoding", "gzip");
                        }
                    }
                    else
                    {
                        object rawResult = null;
                        try
                        {
                            rawResult = method.Invoke(_handler, paramList);
                        }
                        catch (Exception ex)
                        {
                            throw ex;
                        }

                        result = Serialize(rawResult, attr.ResponseContentType);
                        if (attr.EnableCompression && _compressionEncoding == CompressionEncoding.GZip)
                        {
                            result = Compress(result);
                            _response.AppendHeader("Content-Encoding", "gzip");
                        }

                        InsertCache(resultCacheKey, result, attr);
                    }

                }
                else
                {
                    paramList = MapParameters(paramInfo);
                    object rawResult = null;
                    try
                    {
                        rawResult = method.Invoke(_handler, paramList);
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }

                    result = Serialize(rawResult, attr.ResponseContentType);
                    if (attr.EnableCompression && _compressionEncoding == CompressionEncoding.GZip)
                    {
                        result = Compress(result);
                        _response.AppendHeader("Content-Encoding", "gzip");
                    }
                }

                _response.BinaryWrite(result);
                _response.End();
            }
        }

        #endregion

    }
}
